Source code for hysop.backend.device.opencl.opencl_array

# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import numpy as np
from hysop.tools.htypes import check_instance, first_not_None, to_tuple
from hysop.tools.numpywrappers import slices_empty
from hysop.backend.device.opencl import cl, clArray, clTools
from hysop.backend.device.opencl.opencl_env import OpenClEnvironment
from hysop.backend.device.opencl.opencl_array_backend import OpenClArrayBackend
from hysop.core.arrays import MemoryType, MemoryOrdering
from hysop.core.arrays import default_order
from hysop.core.arrays.array import Array


[docs] class OpenClArray(Array): """ OpenCl memory array wrapper (pyopencl.array.Array). """ def __init__(self, handle, backend, **kargs): """ Parameters ---------- handle: pyopencl.array.Array, implementation of this array kargs: arguments for base classes. """ if not isinstance(handle, clArray.Array): msg = "Handle should be a pyopencl.array.Array but got a {}." msg = msg.format(handle.__class__) raise ValueError(msg) if not isinstance(backend, OpenClArrayBackend): msg = "Backend should be a OpenClArrayBackend but got a {}." msg = msg.format(handle.__class__) raise ValueError(msg) if handle.dtype in [np.float16, np.longdouble, np.bool_]: msg = f"{handle.dtype} unsupported yet for OpenCl arrays." raise TypeError(msg) super().__init__(handle=handle, backend=backend, **kargs) # at this time the opencl backend works only with the default_queue # so we enforce it. if (handle.queue is not None) and (handle.queue is not self.default_queue): msg = "pyopencl.Array has been created with a non-default queue." raise RuntimeError(msg) backend.check_queue(handle.queue) self.set_default_queue(self.default_queue)
[docs] def as_symbolic_array(self, name, **kwds): """ Return a symbolic array variable that contain a reference to this array. """ from hysop.symbolic.array import OpenClSymbolicArray return OpenClSymbolicArray(memory_object=self, name=name, **kwds)
[docs] def as_symbolic_buffer(self, name, **kwds): """ Return a symbolic buffer variable that contain a reference to this array. """ from hysop.symbolic.array import OpenClSymbolicBuffer return OpenClSymbolicBuffer(memory_object=self, name=name, **kwds)
[docs] def get_ndim(self): return self._handle.ndim
[docs] def get_shape(self): return to_tuple(self._handle.shape)
[docs] def set_shape(self, shape): self._handle.shape = shape
[docs] def get_size(self): return self._handle.size
[docs] def get_strides(self): return self._handle.strides
[docs] def get_data(self): try: return self._handle.data except clArray.ArrayHasOffsetError: offset = self.offset alignment = self.backend.device.mem_base_addr_align if (offset % alignment) == 0: # try to return a subbuffer try: buf = self.base_data[offset:] buf.__parent = self.base_data return buf except: raise clArray.ArrayHasOffsetError else: raise
[docs] def get_base(self): return self._handle.base_data
def get_offset(self): return self._handle.offset
[docs] def get_dtype(self): return self._handle.dtype
[docs] def get_flags(self): return self._handle.flags
[docs] def get_T(self): return self.wrap(self._handle.T)
[docs] def get_imag(self): return self.backend.imag(self)
[docs] def get_real(self): return self.backend.real(self)
[docs] def get_nbytes(self): return self._handle.nbytes
[docs] def get_int_ptr(self): return self._handle.base_data.int_ptr + self.offset
# array properties ndim = property(get_ndim) shape = property(get_shape, set_shape) offset = property(get_offset) strides = property(get_strides) data = property(get_data) base = property(get_base) dtype = property(get_dtype) flags = property(get_flags) T = property(get_T) imag = property(get_imag) real = property(get_real) size = property(get_size) nbytes = property(get_nbytes) int_ptr = property(get_int_ptr)
[docs] def get_base_data(self): return self._handle.base_data
[docs] def get_offset(self): return self._handle.offset
base_data = property(get_base_data) offset = property(get_offset)
[docs] def ctype(self): """ Equivalent C type corresponding to the numpy.dtype. """ return clTools.dtype_to_ctype(self.dtype)
[docs] def get(self, handle=False, queue=None, ary=None): """ Returns a HostArray, view or copy of this array. """ queue = self.backend.check_queue(queue) if self.size == 0: return None elif self.flags.forc: host_array = self._call("get", queue=queue, ary=ary) else: from hysop.backend.device.opencl.opencl_copy_kernel_launchers import ( OpenClCopyBufferRectLauncher, ) if ary is not None: host_array = ary else: host_array = self.backend.host_array_backend.empty_like(self) kl = OpenClCopyBufferRectLauncher.from_slices( varname="buffer", src=self, dst=host_array ) evt = kl(queue=queue) evt.wait() if handle: return host_array._handle if host_array.size == 1: if host_array.ndim == 0: return host_array._handle if host_array.ndim == 1: return host_array._handle[0] return host_array
# event managment
[docs] def events(self): """ A list of pyopencl.Event instances that the current content of this array depends on. User code may read, but should never modify this list directly. To update this list, instead use the following methods. """ return self.handle.events
[docs] def add_event(self, evt): """ Add evt to events. If events is too long, this method may implicitly wait for a subset of events and clear them from the list. """ self._call("add_event", evt=evt)
[docs] def finish(self): """ Wait for the entire contents of events, clear it. """ self.handle.finish()
# queue and context facilities
[docs] def get_context(self): """ Get the opencl context associated to this array. """ return self.backend.context
[docs] def get_device(self): """ Get the opencl device associated to this array. """ return self.backend.device
[docs] def set_default_queue(self, queue): """ Sets the default queue for this array. """ # at this time the opencl backend works only with the default_queue # so we enforce it. if queue is not self.default_queue: msg = "Default queue override has been disabled for non-default queues." raise RuntimeError(msg) queue = self.backend.check_queue(queue) self._handle.queue = queue
[docs] def reset_default_queue(self): """ Resets the default queue for this array. """ self._handle.queue = None
[docs] def get_default_queue(self): """ Get the default queue for this array. """ return self._handle.queue or self.backend.default_queue
context = property(get_context) device = property(get_device) default_queue = property(get_default_queue, set_default_queue)
[docs] def with_queue(queue): """ Return a copy of self with the default queue set to queue. """ queue = self.backend.check_queue(queue) yield self._call("with_queue", queue=queue)
# Array specific methods
[docs] def view(self, dtype=None): """ Returns view of array with the same data. If dtype is different from current dtype, the actual bytes of memory will be reinterpreted. """ return self._call("view", dtype=dtype)
[docs] def reshape(self, shape, order=default_order): """ Returns view of array with the same data. If dtype is different from current dtype, the actual bytes of memory will be reinterpreted. """ shape = tuple(int(i) for i in shape) return self._call("reshape", *shape, order=order)
[docs] def astype( self, dtype, queue=None, order=MemoryOrdering.SAME_ORDER, casting="unsafe", subok=True, copy=True, ): """ Copy of the array, cast to a specified type. """ self._unsupported_argument("astype", "order", order, MemoryOrdering.SAME_ORDER) self._unsupported_argument("astype", "casting", casting, "unsafe") self._unsupported_argument("astype", "subok", subok, True) self._unsupported_argument("astype", "copy", copy, True) queue = self.backend.check_queue(queue) return self._call("astype", dtype=dtype, queue=queue)
# Cached kernels for efficiency
[docs] def min(self, axis=None, out=None, queue=None, synchronize=True, **kwds): """ Return the minimum along a given axis. On the first call, a kernel launcher is built for efficiency. """ if (axis is None) and (out is None): if not hasattr(self, "_OpenClArray__min_launcher"): self.__min_launcher = self.backend.amin( a=self, axis=axis, out=out, build_kernel_launcher=True, queue=queue, synchronize=False, **kwds, ) evt = self.__min_launcher(queue=queue, synchronize=False) out = self.__min_launcher.out if synchronize: evt.wait() return out.copy() else: return (evt, out) else: super().min( self, axis=axis, out=out, queue=queue, synchronize=synchronize, **kwds )
[docs] def max(self, axis=None, out=None, queue=None, synchronize=True, **kwds): """ Return the maximum along a given axis. On the first call, a kernel launcher is built for efficiency. """ if (axis is None) and (out is None): if not hasattr(self, "_OpenClArray__max_launcher"): self.__max_launcher = self.backend.amax( a=self, axis=axis, out=out, build_kernel_launcher=True, queue=queue, synchronize=False, **kwds, ) evt = self.__max_launcher(queue=queue, synchronize=False) out = self.__max_launcher.out if synchronize: evt.wait() return out.copy() else: return (evt, out) else: super().max( self, axis=axis, out=out, queue=queue, synchronize=synchronize, **kwds )
[docs] def nanmin(self, axis=None, out=None, queue=None, synchronize=True, **kwds): """ Return the minimum along a given axis. On the first call, a kernel launcher is built for efficiency. """ if (axis is None) and (out is None): if not hasattr(self, "_OpenClArray__nanmin_launcher"): self.__nanmin_launcher = self.backend.nanmin( a=self, axis=axis, out=out, build_kernel_launcher=True, queue=queue, synchronize=False, **kwds, ) evt = self.__nanmin_launcher(queue=queue, synchronize=False) out = self.__nanmin_launcher.out if synchronize: evt.wait() return out.copy() else: return (evt, out) else: super().nanmin( self, axis=axis, out=out, queue=queue, synchronize=synchronize, **kwds )
[docs] def nanmax(self, axis=None, out=None, queue=None, synchronize=True, **kwds): """ Return the maximum along a given axis. On the first call, a kernel launcher is built for efficiency. """ if (axis is None) and (out is None): if not hasattr(self, "_OpenClArray__nanmax_launcher"): self.__nanmax_launcher = self.backend.nanmax( a=self, axis=axis, out=out, build_kernel_launcher=True, queue=queue, synchronize=False, **kwds, ) evt = self.__nanmax_launcher(queue=queue, synchronize=False) out = self.__nanmax_launcher.out if synchronize: evt.wait() return out.copy() else: return (evt, out) else: super().nanmax( self, axis=axis, out=out, queue=queue, synchronize=synchronize, **kwds )
[docs] def sum(self, axis=None, out=None, queue=None, synchronize=True, **kwds): """ Return the sum along a given axis. On the first call, a kernel launcher is built for efficiency. """ if (axis is None) and (out is None): if not hasattr(self, "_OpenClArray__sum_launcher"): self.__sum_launcher = self.backend.sum( a=self, axis=axis, out=out, build_kernel_launcher=True, queue=queue, synchronize=False, **kwds, ) evt = self.__sum_launcher(queue=queue, synchronize=False) out = self.__sum_launcher.out if synchronize: evt.wait() return out.copy() else: return (evt, out) else: super().sum( self, axis=axis, out=out, queue=queue, synchronize=synchronize, **kwds )
[docs] def setitem(self, subscript, value, queue=None): queue = first_not_None(queue, self.default_queue) if np.isscalar(value): a = self[subscript] if a is not None: a.fill(value=value, queue=queue) else: try: self.handle.setitem(subscript=subscript, value=value, queue=queue) except: from hysop.backend.device.opencl.opencl_copy_kernel_launchers import ( OpenClCopyBufferRectLauncher, ) kl = OpenClCopyBufferRectLauncher.from_slices( varname="buffer", src=value, dst=self, dst_slices=subscript ) evt = kl(queue=queue) evt.wait()
def __setitem__(self, subscript, value, **kwds): if any((s == 0) for s in self[subscript].shape): return self.setitem(subscript=subscript, value=value, **kwds) def __str__(self): return str(self.get()) def __repr__(self): return repr(self.get())